package com.manda.smart.hub.expression.extend;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import com.googlecode.aviator.FunctionLoader;
import com.googlecode.aviator.runtime.type.AviatorFunction;

import com.manda.smart.hub.expression.annotation.Function;
import com.manda.smart.hub.expression.definition.FunctionMeta;
import com.manda.smart.hub.expression.definition.MethodMeta;
import com.manda.smart.hub.expression.plugin.Plugin;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Stream;

/**
 * @author hongda.li
 */
@Slf4j
public class EnhancedFunctionLoader implements FunctionLoader {

    /**
     * 当前执行器的名称
     */
    private final String runnerName;

    /**
     * 函数扫描器
     */
    private final List<FunctionScanner> scannerList;

    /**
     * 函数缓存
     */
    private final Map<String, AviatorFunction> functions = HashMap.newHashMap(1 << 5);

    public EnhancedFunctionLoader(String runnerName, List<FunctionScanner> scannerList) {
        this.runnerName = runnerName;
        this.scannerList = scannerList;
    }

    @Override
    public AviatorFunction onFunctionNotFound(String name) {
        return functions.get(name);
    }

    public EnhancedFunctionLoader parse() {
        // 重置缓存
        this.functions.clear();

        // 实例化适用于当前执行器的扫描器
        FunctionScanner.Scanner scanner = new FunctionScanner.Scanner(this.runnerName);

        // 执行扫描
        for (FunctionScanner groupScanner : scannerList) {
            groupScanner.doScan(scanner);
        }

        // 将扫描结果转为函数组实例
        scanner.getGroupList().forEach(group -> Stream.of(ReflectUtil.getMethods(group.getClass(), this::matchFuncMethod))
                .map(method -> {
                    FunctionMeta funcMeta = new FunctionMeta(this.getFuncName(method), new MethodMeta(group, method));
                    funcMeta.addPluginList(this.buildPluginList(group.getClass(), method));
                    return funcMeta;
                })
                .map(EnhancedFunction::new)
                .forEach(function -> {
                    String name = function.getName();
                    Assert.isFalse(this.functions.containsKey(name), "The function with the same name already exists : {}", name);

                    this.functions.put(name, function);
                    log.debug("Cache function : {}", name);
                }));

        return this;
    }

    private List<Plugin> buildPluginList(Class<?> type, Method method) {
        return Stream.concat(
                        Stream.of(AnnotationUtil.getAnnotation(type, Function.class).plugins()),
                        Stream.of(AnnotationUtil.getAnnotation(method, Function.class).plugins())
                )
                .distinct()
                .map(Singleton::get)
                .filter(Objects::nonNull)
                .map(Plugin.class::cast)
                .sorted(Comparator.comparingInt(Plugin::order))
                .toList();
    }

    private boolean matchFuncMethod(Method method) {
        return AnnotationUtil.hasAnnotation(method, Function.class)
                && !Modifier.isStatic(method.getModifiers())
                && !Modifier.isPrivate(method.getModifiers());
    }

    private String getFuncName(Method method) {
        return CharSequenceUtil.blankToDefault(
                AnnotationUtil.getAnnotationValue(method, Function.class),
                method.getName()
        );
    }
}
